package cn.isekai.keycloak.avatar.protocol.oidc.mappers;

import cn.isekai.keycloak.avatar.jpa.UserAvatarEntity;
import cn.isekai.keycloak.avatar.spi.ConfigService;
import cn.isekai.keycloak.avatar.storage.AvatarStorageProvider;
import cn.isekai.keycloak.avatar.storage.fs.FSAvatarStorageProvider;
import org.keycloak.models.*;
import org.keycloak.protocol.oidc.mappers.*;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * OpenID Connect Avatar Mapper
 * <p>
 * 将用户头像信息映射到 OpenID Connect token 中的 picture 和 picture_set claim。
 * <p>
 * 功能特性：
 * - 支持多种尺寸的头像输出 (xs: 32px, sm: 48px, md: 64px, lg: 128px, xl: 256px, xxl: 512px)
 * - 自动回退到默认头像机制
 * - 用户设置头像时返回 picture 和 picture_set
 * - 用户未设置头像时仅返回 picture (指向默认头像)
 *
 * @author Dracowyn
 */
public class AvatarMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {

    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();

    static {
        OIDCAttributeMapperHelper.addIncludeInTokensConfig(CONFIG_PROPERTIES, AvatarMapper.class);
    }

    public static final String PROVIDER_ID = "oidc-avatar-mapper";

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return CONFIG_PROPERTIES;
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }

    @Override
    public String getDisplayType() {
        return "User's avatar";
    }

    @Override
    public String getDisplayCategory() {
        return TOKEN_MAPPER_CATEGORY;
    }

    @Override
    public String getHelpText() {
        return "Maps the user's avatar to the OpenID Connect 'picture' and 'picture_set' claims with multiple size support.";
    }

    /**
     * 设置头像相关的声明到 ID Token 中
     * <p>
     * 处理逻辑：
     * 1. 检查用户属性中的 avatarId 或 avatar 字段
     * 2. 如果用户有设置头像，返回 picture 和 picture_set（包含多尺寸）
     * 3. 如果用户未设置头像，仅返回默认头像的 picture
     *
     * @param token            ID Token 实例
     * @param mappingModel     映射模型配置
     * @param userSession      用户会话
     * @param keycloakSession  Keycloak 会话
     * @param clientSessionCtx 客户端会话上下文
     */
    @Override
    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession,
                            KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
        UserModel user = userSession.getUser();
        ConfigService configService = keycloakSession.getProvider(ConfigService.class);
        String realmName = keycloakSession.getContext().getRealm().getName();

        String userAvatarId = getUserAvatarId(user);

        if (userAvatarId != null) {
            processUserAvatarClaims(token, user, userAvatarId, configService, keycloakSession, realmName);
        } else {
            setDefaultAvatarClaim(keycloakSession, token, realmName);
        }
    }

    /**
     * 获取用户头像标识符
     *
     * @param user 用户模型
     * @return 头像标识符，如果用户未设置头像则返回 null
     */
    private String getUserAvatarId(UserModel user) {
        String avatarId = user.getFirstAttribute("avatarId");
        if (avatarId != null && !avatarId.trim().isEmpty()) {
            return avatarId.trim();
        }
        return null;
    }

    /**
     * 处理用户自定义头像的声明设置
     * <p>
     * 根据用户的头像标识符生成多尺寸头像URL集合，包括主头像和头像集合。
     *
     * @param token           ID Token 实例
     * @param user            用户模型
     * @param avatarId        头像标识符
     * @param configService   配置服务
     * @param keycloakSession Keycloak 会话
     * @param realmName       域名
     */
    private void processUserAvatarClaims(IDToken token, UserModel user, String avatarId,
                                         ConfigService configService, KeycloakSession keycloakSession, String realmName) {
        UserAvatarEntity userAvatarEntity = UserAvatarEntity.findByUserId(keycloakSession, user.getId());

        if (userAvatarEntity != null && !userAvatarEntity.getStorage().isEmpty()) {
            AvatarStorageProvider storageProvider = keycloakSession.getProvider(AvatarStorageProvider.class, userAvatarEntity.getStorage());

            if (storageProvider != null) {
                generateAvatarClaims(token, storageProvider, user.getId(), avatarId, configService, keycloakSession, realmName);
                return;
            }

            String fallbackUrl = userAvatarEntity.getFallbackURL();
            if (!fallbackUrl.isEmpty()) {
                token.getOtherClaims().put("picture", fallbackUrl);
                return;
            }
        }

        setDefaultAvatarClaim(keycloakSession, token, realmName);
    }

    /**
     * 生成头像相关的声明信息
     *
     * @param token           ID Token 实例
     * @param storageProvider 存储提供者
     * @param userId          用户标识符
     * @param avatarId        头像标识符
     * @param configService   配置服务
     * @param keycloakSession Keycloak 会话
     * @param realmName       域名
     */
    private void generateAvatarClaims(IDToken token, AvatarStorageProvider storageProvider, String userId,
                                      String avatarId, ConfigService configService, KeycloakSession keycloakSession, String realmName) {
        String baseUri = keycloakSession.getContext().getUri().getBaseUri().toString();

        String mainAvatarPath = storageProvider.getAvatarURL(realmName, userId, avatarId, configService.getDefaultSize(), false);
        String mainAvatarUrl = buildCompleteUrl(baseUri, mainAvatarPath);
        token.getOtherClaims().put("picture", mainAvatarUrl);

        Map<String, String> pictureSet = new HashMap<>();
        for (Map.Entry<String, Integer> sizeEntry : ConfigService.sizeList.entrySet()) {
            String sizeName = sizeEntry.getKey();
            Integer sizePixels = sizeEntry.getValue();
            String sizeAvatarPath = storageProvider.getAvatarURL(realmName, userId, avatarId, sizeName, false);
            String sizeAvatarUrl = buildCompleteUrl(baseUri, sizeAvatarPath);
            String sizeLabel = String.format("%dpx", sizePixels);
            pictureSet.put(sizeLabel, sizeAvatarUrl);
        }
        token.getOtherClaims().put("picture_set", pictureSet);
    }

    /**
     * 构建完整的URL
     *
     * @param baseUri 基础URI
     * @param path    路径
     * @return 完整的URL
     */
    private String buildCompleteUrl(String baseUri, String path) {
        if (path == null || path.isEmpty()) {
            return baseUri;
        }

        if (path.startsWith("http://") || path.startsWith("https://")) {
            return path;
        }

        String cleanBaseUri = baseUri.endsWith("/") ? baseUri.substring(0, baseUri.length() - 1) : baseUri;
        String cleanPath = path.startsWith("/") ? path : "/" + path;

        return cleanBaseUri + cleanPath;
    }

    /**
     * 设置默认头像声明到 token 中
     * <p>
     * 默认头像使用固定格式：realms/{realmName}/avatar/default
     * 只有当默认头像文件存在时才设置 picture 字段
     *
     * @param keycloakSession Keycloak 会话
     * @param token           ID Token 实例
     * @param realmName       域名
     */
    private void setDefaultAvatarClaim(KeycloakSession keycloakSession, IDToken token, String realmName) {
        ConfigService configService = keycloakSession.getProvider(ConfigService.class);
        AvatarStorageProvider storageProvider = configService.getStorageService(keycloakSession);

        if (storageProvider != null) {
            // 检查默认头像是否存在
            if (hasDefaultAvatar(storageProvider, realmName, configService.getDefaultSize())) {
                String defaultAvatarPath = String.format("realms/%s/avatar/default", realmName);
                String baseUri = keycloakSession.getContext().getUri().getBaseUri().toString();
                defaultAvatarPath = buildCompleteUrl(baseUri, defaultAvatarPath);
                token.getOtherClaims().put("picture", defaultAvatarPath);
            }
            // 如果默认头像不存在，则不设置 picture 字段
        }
    }

    /**
     * 检查默认头像是否存在
     *
     * @param storageProvider 存储提供者
     * @param realmName       域名
     * @param defaultSize     默认尺寸
     * @return 默认头像是否存在
     */
    private boolean hasDefaultAvatar(AvatarStorageProvider storageProvider, String realmName, String defaultSize) {
        // 如果是文件系统存储提供者，直接检查文件是否存在
        if (storageProvider instanceof FSAvatarStorageProvider fsProvider) {
            String defaultAvatarPath = fsProvider.getConfig().getDefaultAvatarPath(realmName, defaultSize);
            return new File(defaultAvatarPath).exists();
        }

        // 对于其他存储提供者，使用通用方法
        return storageProvider.getAvatarURL(realmName, "", "", defaultSize, true) != null;
    }
}
